<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Terminal to HTML</title>
  <style>
* {
  box-sizing: border-box;
}

body {
  font-family: 'Courier New', Courier, monospace;
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;
  background: #000;
  color: #0f0;
  line-height: 1.6;
}

h1 {
  color: #0f0;
  margin-bottom: 10px;
  font-weight: bold;
  text-shadow: 0 0 10px #0f0;
}

.instructions {
  background: #001a00;
  border-left: 4px solid #0f0;
  padding: 15px;
  margin-bottom: 20px;
  border-radius: 4px;
  color: #0f0;
}

.paste-area {
  width: 100%;
  min-height: 120px;
  padding: 15px;
  border: 2px dashed #0f0;
  border-radius: 8px;
  background: #001a00;
  color: #0f0;
  font-family: 'Courier New', Courier, monospace;
  font-size: 16px;
  margin-bottom: 20px;
  outline: none;
  transition: border-color 0.3s;
}

.paste-area:focus {
  border-color: #0f0;
  border-style: solid;
  box-shadow: 0 0 10px #0f0;
}

.paste-area::placeholder {
  color: #0a0;
}

.results {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.section {
  background: #001a00;
  border-radius: 8px;
  border: 1px solid #0f0;
  padding: 20px;
  box-shadow: 0 0 20px rgba(0, 255, 0, 0.2);
}

.section h2 {
  margin-top: 0;
  color: #0f0;
  font-size: 18px;
  border-bottom: 2px solid #0f0;
  padding-bottom: 10px;
  margin-bottom: 15px;
  text-shadow: 0 0 5px #0f0;
}

.code-output {
  width: 100%;
  min-height: 200px;
  padding: 15px;
  border: 1px solid #0f0;
  border-radius: 4px;
  background: #000;
  color: #0f0;
  font-family: 'Courier New', Courier, monospace;
  font-size: 14px;
  resize: vertical;
  overflow-x: auto;
  white-space: pre;
}

.button-group {
  display: flex;
  gap: 10px;
  margin-bottom: 15px;
  flex-wrap: wrap;
}

button {
  padding: 10px 20px;
  background: #0f0;
  color: #000;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  font-family: 'Courier New', Courier, monospace;
  font-weight: bold;
  transition: all 0.3s;
  box-shadow: 0 0 10px rgba(0, 255, 0, 0.3);
}

button:hover {
  background: #0c0;
  box-shadow: 0 0 20px rgba(0, 255, 0, 0.5);
}

button:active {
  background: #0a0;
}

button:disabled {
  background: #333;
  color: #666;
  cursor: not-allowed;
  box-shadow: none;
}

button.copied {
  background: #00ff00;
}

.preview-container {
  background: #000;
  border: 1px solid #0f0;
  border-radius: 4px;
  padding: 0;
  overflow-x: auto;
  overflow-y: auto;
  max-width: 100%;
}

.preview-container pre {
  margin: 0;
  padding: 15px;
  white-space: pre;
  overflow-x: visible;
  width: max-content;
  min-width: 100%;
}

.empty-state {
  text-align: center;
  color: #0a0;
  padding: 40px;
  font-size: 18px;
}

.error {
  background: #1a0000;
  border-left: 4px solid #f00;
  padding: 15px;
  margin-bottom: 20px;
  border-radius: 4px;
  color: #f00;
}

.gist-links {
  margin-top: 15px;
  padding: 15px;
  background: #000;
  border: 1px solid #0f0;
  border-radius: 4px;
}

.gist-links a {
  color: #0f0;
  text-decoration: none;
  display: inline-block;
  margin-right: 10px;
  padding: 5px;
  border-bottom: 1px solid #0f0;
}

.gist-links a:hover {
  color: #0ff;
  border-bottom-color: #0ff;
  text-shadow: 0 0 5px #0ff;
}

#authLink {
  color: #0f0;
  cursor: pointer;
  text-decoration: underline;
  background: none;
  border: none;
  padding: 0;
  font-size: inherit;
  box-shadow: none;
  display: inline;
}

#authLink:hover {
  color: #0ff;
  text-shadow: 0 0 5px #0ff;
}

@media (max-width: 768px) {
  body {
    padding: 10px;
  }

  h1 {
    font-size: 24px;
  }

  .button-group {
    flex-direction: column;
  }

  button {
    width: 100%;
  }
}
  </style>
</head>
<body>
  <h1>Terminal to HTML</h1>

  <div class="instructions">
    <strong>Instructions:</strong> Paste terminal output below. Supports RTF (colored terminal output), HTML, or plain text.
  </div>

  <textarea class="paste-area" placeholder="Click here and paste your terminal output (Cmd+V or Ctrl+V)..."></textarea>

  <div class="results" id="results">
    <div class="empty-state">Paste terminal output to see the HTML conversion</div>
  </div>

<script type="module">
const pasteArea = document.querySelector('.paste-area');
const resultsDiv = document.getElementById('results');

function parseRTFColor(colorDef) {
  const redMatch = colorDef.match(/\\red(\d+)/);
  const greenMatch = colorDef.match(/\\green(\d+)/);
  const blueMatch = colorDef.match(/\\blue(\d+)/);

  if (redMatch && greenMatch && blueMatch) {
    const r = parseInt(redMatch[1]);
    const g = parseInt(greenMatch[1]);
    const b = parseInt(blueMatch[1]);
    return `rgb(${r}, ${g}, ${b})`;
  }
  return null;
}

function parseRTFColorTable(rtf) {
  const colorTableMatch = rtf.match(/\{\\colortbl;([^}]+)\}/);
  if (!colorTableMatch) return [];

  const colorTable = [null];
  const colorDefs = colorTableMatch[1].split(';');

  for (const colorDef of colorDefs) {
    if (colorDef.trim()) {
      const color = parseRTFColor(colorDef.trim());
      colorTable.push(color);
    }
  }

  return colorTable;
}

function rtfToHtml(rtf) {
  const colorTable = parseRTFColorTable(rtf);

  let backgroundColor = null;
  let defaultTextColor = null;

  const initialFormatMatch = rtf.match(/\\f0\\fs\d+\s+\\cf(\d+)\s+\\cb(\d+)/);
  if (initialFormatMatch) {
    const textColorIndex = parseInt(initialFormatMatch[1]);
    const bgColorIndex = parseInt(initialFormatMatch[2]);
    defaultTextColor = colorTable[textColorIndex];
    backgroundColor = colorTable[bgColorIndex];
  }

  const pardIndex = rtf.indexOf('\\pard');
  if (pardIndex === -1) {
    throw new Error('Could not find paragraph content');
  }

  const contentPattern = /\\f0\\fs\d+\s+\\cf\d+\s+\\cb\d+\s+\\CocoaLigature\d+\s+/;
  const match = rtf.substring(pardIndex).match(contentPattern);

  if (!match) {
    throw new Error('Could not find content start marker');
  }

  const contentStartOffset = pardIndex + match.index + match[0].length;
  const lastBrace = rtf.lastIndexOf('}');
  let content = rtf.substring(contentStartOffset, lastBrace);

  let html = '';
  let i = 0;
  let currentColor = defaultTextColor;
  let currentBgColor = backgroundColor;
  let isBold = false;

  while (i < content.length) {
    const char = content[i];

    if (char === '\\') {
      let controlWord = '';
      let j = i + 1;

      if (j < content.length && content[j] === "'") {
        j++;
        let hexCode = '';
        while (j < content.length && /[0-9a-fA-F]/.test(content[j]) && hexCode.length < 2) {
          hexCode += content[j];
          j++;
        }
        if (hexCode.length === 2) {
          const charCode = parseInt(hexCode, 16);
          html += String.fromCharCode(charCode);
          i = j;
          continue;
        }
      }

      while (j < content.length && /[a-z]/i.test(content[j])) {
        controlWord += content[j];
        j++;
      }

      let numParam = '';
      while (j < content.length && /[0-9]/.test(content[j])) {
        numParam += content[j];
        j++;
      }

      if (j < content.length && content[j] === ' ') {
        j++;
      }

      if (controlWord === 'cf') {
        const colorIndex = parseInt(numParam);
        currentColor = colorTable[colorIndex] || defaultTextColor;
        i = j;
      } else if (controlWord === 'cb') {
        const colorIndex = parseInt(numParam);
        currentBgColor = colorTable[colorIndex] || backgroundColor;
        i = j;
      } else if (controlWord === 'b') {
        if (numParam === '0') {
          isBold = false;
        } else {
          isBold = true;
        }
        i = j;
      } else if (controlWord === 'uc') {
        i = j;
      } else if (controlWord === 'u') {
        const codePoint = parseInt(numParam);
        if (codePoint < 0) {
          const unsigned = 65536 + codePoint;
          html += String.fromCharCode(unsigned);
        } else {
          html += String.fromCharCode(codePoint);
        }
        if (j < content.length && content[j] === '?') {
          j++;
        }
        i = j;
      } else if (controlWord === '') {
        if (i + 1 < content.length) {
          const nextChar = content[i + 1];
          if (nextChar === '\\') {
            html += '\\';
            i += 2;
          } else if (nextChar === '\n') {
            html += '\n';
            i += 2;
          } else {
            i++;
          }
        } else {
          i++;
        }
      } else {
        i = j;
      }
    } else if (char === '\n') {
      html += '\n';
      i++;
    } else {
      let text = '';

      while (i < content.length && content[i] !== '\\' && content[i] !== '\n') {
        text += content[i];
        i++;
      }

      if (text.length > 0) {
        const styles = [];
        if (currentColor) {
          styles.push(`color: ${currentColor}`);
        }
        if (currentBgColor && currentBgColor !== backgroundColor) {
          styles.push(`background: ${currentBgColor}`);
        }
        if (isBold) {
          styles.push('font-weight: bold');
        }

        if (styles.length > 0) {
          html += `<span style="${styles.join('; ')};">${escapeHtml(text)}</span>`;
        } else {
          html += escapeHtml(text);
        }
      }
    }
  }

  return { html, backgroundColor, defaultTextColor };
}

function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

function wrapInHtmlDocument(content) {
  return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Terminal Output</title>
</head>
<body style="background: black; color: white; margin: 0; padding: 5px;">
${content}
</body>
</html>`;
}

function checkGithubAuth() {
  const token = localStorage.getItem('GITHUB_TOKEN');
  const authLinkContainer = document.getElementById('authLinkContainer');
  const saveGistBtn = document.getElementById('saveGistBtn');
  const privateGistLabel = document.getElementById('privateGistLabel');

  if (token && authLinkContainer && saveGistBtn) {
    authLinkContainer.style.display = 'none';
    saveGistBtn.style.display = 'inline-block';
    if (privateGistLabel) {
      privateGistLabel.style.display = 'inline-flex';
    }
  } else if (authLinkContainer && saveGistBtn) {
    authLinkContainer.style.display = 'inline';
    saveGistBtn.style.display = 'none';
    if (privateGistLabel) {
      privateGistLabel.style.display = 'none';
    }
  }
}

function startAuthPoll() {
  const pollInterval = setInterval(() => {
    if (localStorage.getItem('GITHUB_TOKEN')) {
      checkGithubAuth();
      clearInterval(pollInterval);
    }
  }, 1000);
}

async function createGist(htmlContent) {
  const token = localStorage.getItem('GITHUB_TOKEN');
  if (!token) {
    checkGithubAuth();
    return;
  }

  const saveGistBtn = document.getElementById('saveGistBtn');
  const gistLinks = document.getElementById('gistLinks');
  const privateGistCheckbox = document.getElementById('privateGistCheckbox');
  const isPrivate = privateGistCheckbox ? privateGistCheckbox.checked : true;

  try {
    saveGistBtn.disabled = true;
    saveGistBtn.textContent = 'Saving...';

    const response = await fetch('https://api.github.com/gists', {
      method: 'POST',
      headers: {
        'Authorization': `token ${token}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        description: 'Terminal output HTML',
        public: !isPrivate,
        files: {
          'index.html': {
            content: htmlContent
          }
        }
      })
    });

    if (!response.ok) {
      const error = new Error('Failed to create gist');
      error.status = response.status;
      throw error;
    }

    const data = await response.json();
    const gistId = data.id;
    const gistUrl = data.html_url;
    const previewUrl = `https://gistpreview.github.io/?${gistId}`;

    gistLinks.innerHTML = `
      <div style="margin-bottom: 10px;">Gist created successfully!</div>
      <div style="margin-bottom: 10px;">
        <a href="${previewUrl}">${previewUrl}</a>
      </div>
      <button id="copyUrlBtn" style="margin-right: 10px;">Copy URL</button>
      <a href="${gistUrl}" style="color: #0f0; text-decoration: underline;">View Gist</a>
    `;

    const copyUrlBtn = document.getElementById('copyUrlBtn');
    copyUrlBtn.addEventListener('click', async () => {
      try {
        await navigator.clipboard.writeText(previewUrl);
        copyUrlBtn.textContent = 'Copied!';
        copyUrlBtn.classList.add('copied');
        setTimeout(() => {
          copyUrlBtn.textContent = 'Copy URL';
          copyUrlBtn.classList.remove('copied');
        }, 2000);
      } catch (err) {
        console.error('Failed to copy URL:', err);
      }
    });

  } catch (error) {
    console.error('Gist creation failed:', error);
    // Only remove token for authentication errors (401, 403)
    // Don't log out for rate limiting (429) or other errors
    if (error.status === 401 || error.status === 403) {
      localStorage.removeItem('GITHUB_TOKEN');
      checkGithubAuth();
      gistLinks.innerHTML = '<div style="color: #f00;">Authentication failed. Please authenticate again.</div>';
    } else {
      gistLinks.innerHTML = '<div style="color: #f00;">Failed to create gist. Please try again.</div>';
    }
  } finally {
    saveGistBtn.disabled = false;
    saveGistBtn.textContent = 'Save this to a Gist';
  }
}

pasteArea.addEventListener('paste', async (e) => {
  e.preventDefault();

  const clipboardData = e.clipboardData;
  resultsDiv.innerHTML = '';

  if (!clipboardData) {
    resultsDiv.innerHTML = '<div class="empty-state">No clipboard data detected</div>';
    return;
  }

  let htmlOutput = '';
  let fullHtml = '';

  // Try to get HTML first
  const htmlData = clipboardData.getData('text/html');

  if (htmlData) {
    // HTML is available, use it directly
    htmlOutput = htmlData;
    fullHtml = wrapInHtmlDocument(htmlData);
  } else {
    // Try RTF
    const rtfData = clipboardData.getData('text/rtf');

    if (rtfData) {
      try {
        const result = rtfToHtml(rtfData);
        htmlOutput = result.html;
        const backgroundColor = result.backgroundColor;
        const defaultTextColor = result.defaultTextColor;

        let preStyle = '';
        const styles = [];
        if (backgroundColor) {
          styles.push(`background: ${backgroundColor}`);
        }
        if (defaultTextColor) {
          styles.push(`color: ${defaultTextColor}`);
        }
        if (styles.length > 0) {
          preStyle = ` style="${styles.join('; ')}; padding: 15px; border-radius: 4px;"`;
        }
        fullHtml = wrapInHtmlDocument(`<pre${preStyle}>${htmlOutput}</pre>`);
      } catch (error) {
        resultsDiv.innerHTML = `<div class="error">Error parsing RTF: ${error.message}</div>`;
        return;
      }
    } else {
      // Fall back to plain text
      const plainText = clipboardData.getData('text/plain');

      if (plainText) {
        htmlOutput = escapeHtml(plainText);
        fullHtml = wrapInHtmlDocument(`<pre>${htmlOutput}</pre>`);
      } else {
        resultsDiv.innerHTML = '<div class="error">No supported format detected in clipboard.</div>';
        return;
      }
    }
  }

  // Create HTML code section
  const codeSection = document.createElement('div');
  codeSection.className = 'section';
  codeSection.innerHTML = `
    <h2>HTML Code</h2>
    <div class="button-group">
      <button id="copyHtmlBtn">Copy HTML</button>
      <span id="authLinkContainer" style="display: none;">
        <button id="authLink">Authenticate with GitHub</button>
      </span>
      <button id="saveGistBtn" style="display: none;">Save this to a Gist</button>
      <label id="privateGistLabel" style="display: none; color: #0f0; align-items: center; cursor: pointer; font-size: 14px; margin-left: 10px;">
        <input type="checkbox" id="privateGistCheckbox" checked style="margin-right: 5px; cursor: pointer;">
        Private Gist
      </label>
    </div>
    <textarea class="code-output" id="htmlOutput" readonly>${escapeHtml(fullHtml)}</textarea>
    <div id="gistLinks" class="gist-links" style="display: none;"></div>
  `;
  resultsDiv.appendChild(codeSection);

  const copyHtmlBtn = document.getElementById('copyHtmlBtn');
  const htmlOutputTextarea = document.getElementById('htmlOutput');
  const authLink = document.getElementById('authLink');
  const saveGistBtn = document.getElementById('saveGistBtn');
  const gistLinksDiv = document.getElementById('gistLinks');

  copyHtmlBtn.addEventListener('click', async () => {
    try {
      await navigator.clipboard.writeText(htmlOutputTextarea.value);
      copyHtmlBtn.textContent = 'Copied!';
      copyHtmlBtn.classList.add('copied');
      setTimeout(() => {
        copyHtmlBtn.textContent = 'Copy HTML';
        copyHtmlBtn.classList.remove('copied');
      }, 2000);
    } catch (err) {
      console.error('Failed to copy HTML:', err);
    }
  });

  authLink.addEventListener('click', () => {
    window.open('https://tools.simonwillison.net/github-auth', 'github-auth', 'width=600,height=800');
    startAuthPoll();
  });

  saveGistBtn.addEventListener('click', () => {
    gistLinksDiv.style.display = 'block';
    createGist(fullHtml);
  });

  checkGithubAuth();

  // Create preview section
  const previewSection = document.createElement('div');
  previewSection.className = 'section';
  previewSection.innerHTML = `
    <h2>Preview</h2>
    <div class="preview-container">
      ${fullHtml}
    </div>
  `;
  resultsDiv.appendChild(previewSection);
});

// Clear the textarea on paste
pasteArea.addEventListener('paste', () => {
  setTimeout(() => {
    pasteArea.value = '';
  }, 0);
});
</script>
</body>
</html>
